package com.szjz.elasticsearch.eshelper.service.Impl;

import com.alibaba.fastjson.JSONObject;
import com.szjz.elasticsearch.eshelper.animation.ESField;
import com.szjz.elasticsearch.eshelper.animation.ESFieldIgnore;
import com.szjz.elasticsearch.eshelper.animation.ESTable;
import com.szjz.elasticsearch.eshelper.property.ESRestClientProperties;
import com.szjz.elasticsearch.eshelper.service.ESService;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@Slf4j
@Service
public class ESServiceImpl implements ESService {

	@Autowired
	private ESRestClientProperties esRestClientProperties;
	@Autowired
	private RestHighLevelClient restHighLevelClient;

	/**
	 * 用于项目启动的时候创建索引库
	 * 根据class类创建索引库
	 */
	@Override
	public synchronized Boolean createIndex(Class clazz) {
		ESTable esTable = (ESTable) clazz.getAnnotation(ESTable.class);
		String idxName = esTable.indexName();
		if (!indexExist(idxName)) {
			return createMapping(clazz, esTable, idxName);
		} else {
			log.warn("【ES创建索引库】索引库：{} 已经存在", idxName);
		}
		return true;
	}

	/**
	 * 用于保存数据的时候创建索引库
	 * 根据索引库名称和class创建索引库
	 */
	@Override
	public synchronized Boolean createIndex(String idxName, Class clazz) {
		if (!indexExist(idxName)) {
			ESTable esTable = (ESTable) clazz.getAnnotation(ESTable.class);
			return createMapping(clazz, esTable, idxName);
		}
		return true;
	}

	/**
	 * 配置索引库的设置信息
	 *
	 * @param clazz   class
	 * @param esTable EsTable 注解
	 * @param idxName 索引库名称
	 * @return
	 */
	private boolean createMapping(Class clazz, ESTable esTable, String idxName) {
		String mappingContent = getMappingContent(clazz, esTable.dynamic());
		CreateIndexRequest request = new CreateIndexRequest(idxName);
		// ** 以ESTable中的设置为主，如果为没有设置，则使用全局配置 **
		// 分片数
		int shardNum = esTable.shardNum() == 0 ? esRestClientProperties.getShardNum() : esTable.shardNum();
		// 副本数
		int replicaNum = esTable.replicaNum() == 0 ? esRestClientProperties.getReplicaNum() : esTable.replicaNum();
		// 默认分页容量
		int size = esTable.size() == 0 ? esRestClientProperties.getSize() : esTable.size();
		// 刷新频率
		String refreshInterval = "".equals(esTable.refreshInterval()) ? esRestClientProperties.getRefreshInterval() : esTable.refreshInterval();

		request.settings(Settings.builder()
			.put("index.number_of_shards", shardNum)
			.put("index.number_of_replicas", replicaNum)
			.put("index.max_result_window", size)
			.put("index.refresh_interval", refreshInterval)
		);

		request.mapping(mappingContent, XContentType.JSON);
		CreateIndexResponse res = null;
		try {
			res = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
		} catch (Exception e) {
			e.printStackTrace();
			log.error("【ES-创建索引库】初始化" + idxName + "失败  " + e.getMessage());
			return false;
		}
		if (!res.isAcknowledged()) {
			log.error("【ES-创建索引库】初始化" + idxName + "失败");
			return false;
		}
		log.info("【ES-创建索引库】成功创建索引库：{}", idxName);
		return true;
	}

	/**
	 * 判断索引库是否存在
	 */
	@SneakyThrows
	@Override
	public boolean indexExist(String idxName) {
		GetIndexRequest request = new GetIndexRequest(idxName);
		return restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
	}


	/**
	 * 删除index
	 */
	@Override
	public void deleteIndex(String idxName) {
		try {
			if (!indexExist(idxName)) {
				log.info("【ES-删除索引库】索引库：{} 不存在", idxName);
				return;
			}
			AcknowledgedResponse delete = restHighLevelClient.indices().delete(new DeleteIndexRequest(idxName), RequestOptions.DEFAULT);
			if (delete.isAcknowledged()) {
				log.info("【ES-删除索引库】删除索引库成功：{}", idxName);
			} else {
				log.error("【ES-删除索引库】删除索引库失败：{}", idxName);
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}


	/**
	 * 创建映射内容
	 *
	 * @param clazz
	 * @param dynamic
	 * @return
	 */
	@Override
	public String getMappingContent(Class clazz, Boolean dynamic) {
		// 获取所有字段
		List<Field> declaredFields = Arrays.asList(clazz.getDeclaredFields());
		List<Field> list = new ArrayList<>(declaredFields);
		while (clazz.getSuperclass() != null) {
			Class superclassSuperclass = clazz.getSuperclass();
			List<Field> fields = Arrays.asList(superclassSuperclass.getDeclaredFields());
			list.addAll(fields);
			clazz = superclassSuperclass;
		}
		// 获取所有没有被@ESFieldIgnore修饰的字段
		List<Field> notIgnoreFields = list.stream().filter(field -> field.getAnnotation(ESFieldIgnore.class) == null && !"serialVersionUID".equals(field.getName())).collect(Collectors.toList());

		if (notIgnoreFields.size() < 1) {
			throw new RuntimeException("【ES】请在类" + clazz.getSimpleName() + "中添加至少一个有效属性!");
		}

		// 获取被 @ESField 修饰的字段
		List<Field> esFields = notIgnoreFields.stream().filter(field -> field.getAnnotation(ESField.class) != null).collect(Collectors.toList());
		Map<String, Map<String, Object>> mappingContentMap = new HashMap<>();
		esFields.forEach(field -> {
			ESField annotation = field.getAnnotation(ESField.class);
			Map<String, Object> map = new ConcurrentHashMap<>();
			boolean index = annotation.index();
			map.put("index", index);
			String type = annotation.type();
			map.put("type", type);
			boolean docValues = annotation.docValues();
			map.put("doc_values", docValues);
			mappingContentMap.put(field.getName(), map);
		});

		// 获取没有被 @ESField 修饰的字段
		List<Field> notESFields = notIgnoreFields.stream().filter(field -> field.getAnnotation(ESField.class) == null).collect(Collectors.toList());
		notESFields.forEach(field -> {
			Map<String, Object> map = new ConcurrentHashMap<>();
			map.put("index", true);
			String type = field.getType().getSimpleName();
			type = "String".equals(type) ? "keyword" : type.toLowerCase();
			map.put("type", type);
			map.put("doc_values", false);
			mappingContentMap.put(field.getName(), map);
		});

		if (esRestClientProperties.getDebug()) {
			log.info("【ES】{}中有效字段：size: {}", clazz.getSimpleName(), notIgnoreFields.size());
			log.info("【ES】{}中被 @ESField 修饰的字段：size: {}", clazz.getSimpleName(), esFields.size());
			log.info("【ES】{}中没有被 @ESField 修饰的字段：size: {}", clazz.getSimpleName(), notESFields.size());
		}

		JSONObject jsonObject = new JSONObject();
		jsonObject.put("dynamic", dynamic);
		jsonObject.put("properties", mappingContentMap);
		log.info("【ES】创建索引库 mappingContent: {}", jsonObject.toJSONString());
		return jsonObject.toJSONString();
	}


	/**
	 * @param idxName
	 * @param builder
	 * @since
	 */
	@Override
	public void deleteByQuery(String idxName, QueryBuilder builder) {
		DeleteByQueryRequest request = new DeleteByQueryRequest(idxName);
		request.setQuery(builder);
		//设置批量操作数量,最大为10000
		request.setBatchSize(1000000);
		request.setConflicts("proceed");
		try {
			restHighLevelClient.deleteByQuery(request, RequestOptions.DEFAULT);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}


}
